<!doctype html>
<title>RTCConfiguration rtcpMuxPolicy</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="RTCConfiguration-helper.js"></script>
<script src="RTCPeerConnection-helper.js"></script>
<script>
  'use strict';

  // Test is based on the following editor draft:
  // https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html

  // The following helper function is called from RTCConfiguration-helper.js:
  //   config_test

  /*
    [Constructor(optional RTCConfiguration configuration)]
    interface RTCPeerConnection : EventTarget {
      RTCConfiguration                   getConfiguration();
      void                               setConfiguration(RTCConfiguration configuration);
      ...
    };

    dictionary RTCConfiguration {
      RTCRtcpMuxPolicy         rtcpMuxPolicy = "require";
      ...
    };

    enum RTCRtcpMuxPolicy {
      "negotiate",
      "require"
    };
  */

  test(() => {
    const pc = new RTCPeerConnection();
    assert_equals(pc.getConfiguration().rtcpMuxPolicy, 'require');
  }, `new RTCPeerConnection() should have default rtcpMuxPolicy require`);

  test(() => {
    const pc = new RTCPeerConnection({ rtcpMuxPolicy: undefined });
    assert_equals(pc.getConfiguration().rtcpMuxPolicy, 'require');
  }, `new RTCPeerConnection({ rtcpMuxPolicy: undefined }) should have default rtcpMuxPolicy require`);

  test(() => {
    const pc = new RTCPeerConnection({ rtcpMuxPolicy: 'require' });
    assert_equals(pc.getConfiguration().rtcpMuxPolicy, 'require');
  }, `new RTCPeerConnection({ rtcpMuxPolicy: 'require' }) should succeed`);

  /*
    4.3.1.1.  Constructor
      3.  If configuration.rtcpMuxPolicy is negotiate, and the user agent does not
          implement non-muxed RTCP, throw a NotSupportedError.
   */
  test(() => {
    let pc;
    try {
      pc = new RTCPeerConnection({ rtcpMuxPolicy: 'negotiate' });
    } catch(err) {
      // NotSupportedError is a DOMException with code 9
      if(err.code === 9 && err.name === 'NotSupportedError') {
        // ignore error and pass test if negotiate is not supported
        return;
      } else {
        throw err;
      }
    }

    assert_equals(pc.getConfiguration().rtcpMuxPolicy, 'negotiate');

  }, `new RTCPeerConnection({ rtcpMuxPolicy: 'negotiate' }) may succeed or throw NotSupportedError`);

  config_test(makePc => {
    assert_throws_js(TypeError, () =>
      makePc({ rtcpMuxPolicy: null }));
  }, `with { rtcpMuxPolicy: null } should throw TypeError`);

  config_test(makePc => {
    assert_throws_js(TypeError, () =>
      makePc({ rtcpMuxPolicy: 'invalid' }));
  }, `with { rtcpMuxPolicy: 'invalid' } should throw TypeError`);

  /*
    4.3.2.  Set a configuration
      6.  If configuration.rtcpMuxPolicy is set and its value differs from the
          connection's rtcpMux policy, throw an InvalidModificationError.
   */

  test(() => {
    const pc = new RTCPeerConnection({ rtcpMuxPolicy: 'require' });
    assert_idl_attribute(pc, 'setConfiguration');
    assert_throws_dom('InvalidModificationError', () =>
      pc.setConfiguration({ rtcpMuxPolicy: 'negotiate' }));

  }, `setConfiguration({ rtcpMuxPolicy: 'negotiate' }) with initial rtcpMuxPolicy require should throw InvalidModificationError`);

  test(() => {
    let pc;
    try {
      pc = new RTCPeerConnection({ rtcpMuxPolicy: 'negotiate' });
    } catch(err) {
      // NotSupportedError is a DOMException with code 9
      if(err.code === 9 && err.name === 'NotSupportedError') {
        // ignore error and pass test if negotiate is not supported
        return;
      } else {
        throw err;
      }
    }

    assert_idl_attribute(pc, 'setConfiguration');
    assert_throws_dom('InvalidModificationError', () =>
      pc.setConfiguration({ rtcpMuxPolicy: 'require' }));

  }, `setConfiguration({ rtcpMuxPolicy: 'require' }) with initial rtcpMuxPolicy negotiate should throw InvalidModificationError`);

  test(() => {
    let pc;
    pc = new RTCPeerConnection({ rtcpMuxPolicy: 'require' });
    // default rtcpMuxPolicy is 'require', so this is allowed
    pc.setConfiguration({});
    assert_equals(pc.getConfiguration().rtcpMuxPolicy, 'require');
  }, `setConfiguration({}) with initial rtcpMuxPolicy require should leave rtcpMuxPolicy to require`);

  test(() => {
    let pc;
    try {
      pc = new RTCPeerConnection({ rtcpMuxPolicy: 'negotiate' });
    } catch(err) {
      // NotSupportedError is a DOMException with code 9
      if(err.code === 9 && err.name === 'NotSupportedError') {
        // ignore error and pass test if negotiate is not supported
        return;
      } else {
        throw err;
      }
    }

    assert_idl_attribute(pc, 'setConfiguration');
    // default value for rtcpMuxPolicy is require
    assert_throws_dom('InvalidModificationError', () =>
      pc.setConfiguration({}));

  }, `setConfiguration({}) with initial rtcpMuxPolicy negotiate should throw InvalidModificationError`);

  /*
    Coverage Report

      Tested    2
      Total     2
   */
  const FINGERPRINT_SHA256 = '00:00:00:00:00:00:00:00:00:00:00:00:00' +
      ':00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00';
  const ICEUFRAG = 'someufrag';
  const ICEPWD = 'somelongpwdwithenoughrandomness';

  promise_test(async t => {
    // audio-only SDP offer without BUNDLE and rtcp-mux.
    const sdp = 'v=0\r\n' +
        'o=- 166855176514521964 2 IN IP4 127.0.0.1\r\n' +
        's=-\r\n' +
        't=0 0\r\n' +
        'm=audio 9 UDP/TLS/RTP/SAVPF 111\r\n' +
        'c=IN IP4 0.0.0.0\r\n' +
        'a=rtcp:9 IN IP4 0.0.0.0\r\n' +
        'a=ice-ufrag:' + ICEUFRAG + '\r\n' +
        'a=ice-pwd:' + ICEPWD + '\r\n' +
        'a=fingerprint:sha-256 ' + FINGERPRINT_SHA256 + '\r\n' +
        'a=setup:actpass\r\n' +
        'a=mid:audio1\r\n' +
        'a=sendonly\r\n' +
        'a=rtcp-rsize\r\n' +
        'a=rtpmap:111 opus/48000/2\r\n';
    const pc = new RTCPeerConnection({rtcpMuxPolicy: 'require'});
    t.add_cleanup(() => pc.close());

    return promise_rejects_dom(t, 'InvalidAccessError', pc.setRemoteDescription({type: 'offer', sdp}));
  }, 'setRemoteDescription throws InvalidAccessError when called with an offer without rtcp-mux and rtcpMuxPolicy is set to require');

  promise_test(async t => {
    // audio-only SDP answer without BUNDLE and rtcp-mux.
    // Also omitting a=mid in order to avoid parsing it from the offer as this needs to match.
    const sdp = 'v=0\r\n' +
        'o=- 166855176514521964 2 IN IP4 127.0.0.1\r\n' +
        's=-\r\n' +
        't=0 0\r\n' +
        'm=audio 9 UDP/TLS/RTP/SAVPF 111\r\n' +
        'c=IN IP4 0.0.0.0\r\n' +
        'a=rtcp:9 IN IP4 0.0.0.0\r\n' +
        'a=ice-ufrag:' + ICEUFRAG + '\r\n' +
        'a=ice-pwd:' + ICEPWD + '\r\n' +
        'a=fingerprint:sha-256 ' + FINGERPRINT_SHA256 + '\r\n' +
        'a=setup:active\r\n' +
        'a=sendonly\r\n' +
        'a=rtcp-rsize\r\n' +
        'a=rtpmap:111 opus/48000/2\r\n';
    const pc = new RTCPeerConnection({rtcpMuxPolicy: 'require'});
    t.add_cleanup(() => pc.close());

    const offer = await generateAudioReceiveOnlyOffer(pc);
    await pc.setLocalDescription(offer);
    return promise_rejects_dom(t, 'InvalidAccessError', pc.setRemoteDescription({type: 'answer', sdp}));
  }, 'setRemoteDescription throws InvalidAccessError when called with an answer without rtcp-mux and rtcpMuxPolicy is set to require');
</script>
